package org.iabako.server.serviceimpl.business;

import org.apache.log4j.Logger;
import org.dozer.DozerBeanMapperSingletonWrapper;
import org.iabako.server.dao.JpaDAO;
import org.iabako.server.dao.business.ClientDAO;
import org.iabako.server.dao.business.CustomFieldDAO;
import org.iabako.server.dao.business.QueryDefinitionDAO;
import org.iabako.shared.dto.SuggestItemDTO;
import org.iabako.shared.entity.business.*;
import org.iabako.shared.entity.enumeration.CustomFieldType;
import org.iabako.shared.entity.enumeration.FinancialStatusClient;
import org.iabako.shared.entity.enumeration.FinancialStatusSale;
import org.iabako.shared.entity.enumeration.TrackingType;
import org.iabako.shared.entity.user.Enterprise;
import org.iabako.shared.exception.IabakoActionForbiddenException;
import org.iabako.shared.exception.IabakoException;
import org.iabako.shared.exception.IabakoUniqueConstraintException;
import org.iabako.shared.iservice.business.ClientService;
import org.iabako.shared.iservice.business.TrackingService;
import org.iabako.shared.querydefinition.SearchMotorQueryEnum;
import org.iabako.shared.querydefinition.columndefinition.ClientColumnEnum;
import org.iabako.shared.querydefinition.columndefinition.ColumnDefinition;
import org.iabako.shared.querydefinition.dto.QueryDefinitionDTO;
import org.iabako.shared.querydefinition.dto.QueryOperatorEnum;
import org.iabako.shared.tools.GenericTools;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * TODO javadoc
 * Date: 24/11/13
 * Time: 18:22
 */
@Service("clientService")
public class ClientServiceImpl extends AbstractServiceImpl implements ClientService {

    static Logger log = Logger.getLogger(ClientServiceImpl.class.getName());

    @Autowired
    private ClientDAO clientDAO;
    @Autowired
    private CustomFieldDAO customFieldDAO;
    @Autowired
    private QueryDefinitionDAO queryDefinitionDAO;
    @Autowired
    private TrackingService trackingService;

    @Override
    protected JpaDAO getDAO() {
        return clientDAO;
    }

    public QueryDefinitionDTO getSearchQueryDefinitionDTO(QueryDefinitionDTO qd) {
        qd.setSearchMotorQuery(SearchMotorQueryEnum.ClientSearch);
        qd.addCriteria(ClientColumnEnum.clientEnterprise, QueryOperatorEnum.IN, (ArrayList) getSessionUser().getEnterprise().getVisibleEnterprises());

        queryDefinitionDAO.executeQueryDefinition(qd);
        return qd;
    }

    public List<SuggestItemDTO> getSuggestions(String req) {

        List<SuggestItemDTO> suggestions = new ArrayList<SuggestItemDTO>(10);

        List<Object[]> results = clientDAO.getNamesBySuggestion(req);
        for (Object[] result : results) {
            suggestions.add(new SuggestItemDTO(result[0] + "", (String) result[1], (String) result[2], (String) result[3], (String) result[4]));
        }

        return suggestions;
    }

    public AbstractEntity getByHiddenSelectionCode(String hiddenSelectionCode) {
        return getById(Long.parseLong(hiddenSelectionCode));
    }

    public QueryDefinitionDTO setSearchCriteriaFromSuggest(QueryDefinitionDTO qd, Long hiddenSelectionCode, String suggestionValue) {
        if (!GenericTools.isEmpty(hiddenSelectionCode)) {
            qd.addCriteria(ClientColumnEnum.clientId, QueryOperatorEnum.EQUALS, hiddenSelectionCode);
            return qd;
        }
        return setSearchCriteriaFromSuggest(qd,
                suggestionValue,
                new ColumnDefinition[]{ClientColumnEnum.number, ClientColumnEnum.name, ClientColumnEnum.email, ClientColumnEnum.address1});
    }

    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public List<Payment> getPayments(Client client) {
        if (client == null || client.getId() == null) {
            return null;
        }
        return clientDAO.getPayments(client);
    }

    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public List<Installment> getInstallments(Client client) {
        if (client == null || client.getId() == null) {
            return null;
        }
        return clientDAO.getInstallments(client);
    }

    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public Client calculateStatus(Client client) {
        if (client == null || client.getId() == null) {
            return client;
        }
        //OK by default
        client.setStatus(FinancialStatusClient._3_OK);

        List<Sale> sales = clientDAO.getSales(client);

        if (!GenericTools.isEmpty(sales)) {
            boolean oneWithFutureInstallments = false;
            for (Sale sale : sales) {
                if (sale.getStatus() == FinancialStatusSale._1_inDebt) {
                    client.setStatus(FinancialStatusClient._1_inDebt);
                    break;
                } else if (sale.getStatus() == FinancialStatusSale._2_withFutureInstallments) {
                    oneWithFutureInstallments = true;
                }
            }
            if (client.getStatus() != FinancialStatusClient._1_inDebt) {
                if (oneWithFutureInstallments) {
                    client.setStatus(FinancialStatusClient._2_withFutureInstallments);
                } else {
                    client.setStatus(FinancialStatusClient._3_OK);
                }
            }
        }
        return clientDAO.merge(client);
    }

    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public List<CustomFieldValue> getCustomFieldsValue(Client client) {
        List<CustomField> customFields = customFieldDAO.getCustomFields();
        List<CustomFieldValue> values = new ArrayList<CustomFieldValue>();
        for (CustomField field : customFields) {
            CustomFieldValue value = new CustomFieldValue();
            value.setCustomField(field);
            value.setClient(client);
            values.add(value);
        }

        if (client == null || client.getId() == null) {

            return values;

        } else {
            List<CustomFieldValue> clientCustomFields = clientDAO.getCustomFieldsValue(client);
            Map<CustomFieldValue, CustomFieldValue> valuesToReplace = new HashMap<CustomFieldValue, CustomFieldValue>();
            for (CustomFieldValue value : values) {
                for (CustomFieldValue clientValue : clientCustomFields) {
                    if (clientValue.getCustomField().equals(value.getCustomField())) {
                        valuesToReplace.put(value, clientValue);
                    }
                }
            }

            for (CustomFieldValue valueToReplace : valuesToReplace.keySet()) {
                int index = values.indexOf(valueToReplace);
                values.remove(valueToReplace);
                values.add(index, valuesToReplace.get(valueToReplace));
            }

            return values;
        }
    }

    @Override
    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public Boolean remove(Long id) {
        clientDAO.deleteAttachedObjects(id);
        trackingService.addTrackingToUserSession(TrackingType.clientDelete, (Client) getById(id));
        return super.remove(id);
    }

    /**
     * Method to persist Client modified from GWT client (navigator/JS world)
     * We can't pass by cascading persist because GWT does not support (serialize)
     * PersistentBag and Proxy/Lazy hibernate objects
     *
     * @param client            Client object from GWT client (navigator/JS world)
     * @param customFieldValues List of CustomFieldValues from GWT client (navigator/JS world)
     */
    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public Client save(Client client, List<CustomFieldValue> customFieldValues) throws IabakoException {
        Client clientFromDB;
        Enterprise enterprise = getEnterpriseFromSessionUser();

        if (enterprise != null && enterprise.isDemo()) {
            throw new IabakoActionForbiddenException("action_forbidden_for_demo_title", "action_forbidden_for_demo_text", true);
        }

        if (!getDAO().isUniqueNumber(client)) {
            throw new IabakoUniqueConstraintException(
                    messages.getLabel("validation_client_unique_constraint_error_title"),
                    messages.getLabel("validation_client_unique_constraint_error_text", client.getNumber()), true, true);
        }

        if (client.getId() != null && client.isDisabled() && clientDAO.hasInstallments(client)) {
            throw new IabakoException(messages.getLabel("client_disabled_not_possible_title"),
                    messages.getLabel("client_disabled_not_possible_text"), true, true);
        }

        if (GenericTools.isEmpty(client.getId())) {
            trackingService.addTrackingToUserSession(TrackingType.clientNew, client);
            clientFromDB = client;

        } else {
            trackingService.addTrackingToUserSession(TrackingType.clientModify, client);
            clientFromDB = clientDAO.findById(client.getId());

            //To copy client's attributes modified into persisted service
            DozerBeanMapperSingletonWrapper.getInstance().map(client, clientFromDB);
        }

        //To copy service's customFieldValues modified into persisted installmentsDefinition
        for (CustomFieldValue value : customFieldValues) {
            //If it's a client creation, CustomFieldValues client hasn't been set
            value.setClient(clientFromDB);
            //Enterprise is bypassed by GWT-layer
            if (enterprise.getParentEnterprise() != null) {
                //Do not change Enterprise!
                value.getCustomField().setEnterprise(enterprise.getParentEnterprise());
            } else {
                value.getCustomField().setEnterprise(enterprise);
            }

            if (value.getCustomField().getCustomFieldType() == CustomFieldType.date
                    && !GenericTools.isEmpty(value.getDateValueString())) {
                value.setDateValue(GenericTools.getDateFromString(value.getDateValueString()));
            }
        }
        clientFromDB.setCustomFieldValues(customFieldValues);

        save(clientFromDB);

        return clientFromDB;
    }

    public Client save(Client client) throws IabakoActionForbiddenException {
        Enterprise enterprise = getEnterpriseFromSessionUser();

        client.setBirthday(GenericTools.getDateFromString(client.getBirthdayString()));

        if (client.getEnterprise() == null) {
            if (client.getId() != null && enterprise.getAllRelatedEnterprises().size() > 1) {
                //Do not change Enterprise!
                client.setEnterprise(clientDAO.findById(client.getId()).getEnterprise());
            } else {
                client.setEnterprise(enterprise);
            }
        }

        setBusinessTrackInfo(client);

        client = (Client) super.save(client);
        return calculateStatus(client);
    }

    public Client getClientByEmail(Enterprise enterprise, String email) {
        return clientDAO.getClientByEmail(enterprise, email);
    }

    public boolean hasInstallments(Client client) {
        return clientDAO.hasInstallments(client);
    }
}
